﻿using HY.Web.Models;
using Microsoft.AspNetCore.Diagnostics;
using Newtonsoft.Json;

namespace HY.Web
{
    /// <summary>
    /// 使用异常中间件处理异常，需注册：app.UseExceptionHandler(builder => builder.Use(CustomExceptionHandler.GlobalExceptionHandler));
    /// </summary>
    public class CustomExceptionHandler
    {
        private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        async public static Task GlobalExceptionHandler(HttpContext httpContext, Func<Task> next)
        {
            //该信息由ExceptionHandlerMiddleware中间件提供，里面包含了ExceptionHandlerMiddleware中间件捕获到的异常信息。
            var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
            logger.Error(exceptionDetails?.Error, $"({nameof(GlobalExceptionHandler)})异常地址:{httpContext.Request.Path}");

            string requestType = httpContext.Request.Headers["X-Requested-With"];
            if (!string.IsNullOrEmpty(requestType) && requestType.Equals("XMLHttpRequest", StringComparison.CurrentCultureIgnoreCase))
            {
                //Ajax请求内部错误
                ReturnModel returnModel = new()
                {
                    Code = StatusCodes.Status500InternalServerError,
                    Message = exceptionDetails?.Error.Message ?? "系统内部错误"
                };
                httpContext.Response.ContentType = "application/json;charset=utf-8";
                httpContext.Response.StatusCode = StatusCodes.Status200OK;
                await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(returnModel));
            }
            else
            {
                httpContext.Response.Redirect($"/Home/Error/{httpContext.Response.StatusCode}");
            }
        }
    }

    /*
     * 中间件和过滤器的方式来处理异常的区别：拦截范围的不同
    1.IExceptionFilter作为一种过滤器，它需要在控制器发现错误之后将错误信息提交给它处理，因此它的异常处理范围是控制器内部。如果我们想捕获进入控制器之前的一些错误，IExceptionFilter是捕获不到的。而对于ExceptionHandlerMiddleware异常中间件来说就很容易了，它作为第一个中间件被添加到管道中，在它之后发生的任何异常都可以捕获的到
    2.那么为什么要有两种异常处理的方式呢？只使用ExceptionHandlerMiddleware中间件处理异常不可以吗？它可以捕获任何时候发生的异常，为什么还要有过滤器呢？如果你想在控制器发生异常时快速捕获和处理异常，那么使用过滤器处理异常是非常不错的选择。如果是控制器内部发生了异常，首先是由过滤器捕获到异常，最后才是中间件捕获到异常
    3.我们在自定义过滤器的时候有这样一段代码：context.ExceptionHandled = true;如果在自定义过滤器中将异常标记为已经处理之后，则第一个异常处理中间件就认为没有错误了，不会进入到处理逻辑中了。所以，如果不把 ExceptionHandled属性设置为true，可能出现异常处理结果被覆盖的情况
     */

    /// <summary>
    /// 需添加注册builder.Services.AddControllersWithViews(options =>{ options.Filters.Add<CustomExceptionFilter>(); });
    /// </summary>
    //public class CustomExceptionFilter : IAsyncExceptionFilter
    //{
    //    public async Task OnExceptionAsync(ExceptionContext context)
    //    {
    //        //这里获取到的Response.StatusCode好像总是200
    //        Common.Log.Error($"异常请求:{context.HttpContext.Request.Path}", context.Exception);
    //        if (context.ExceptionHandled == false)
    //        {
    //            string requestType = context.HttpContext.Request.Headers["X-Requested-With"];
    //            if (!string.IsNullOrEmpty(requestType) && requestType.Equals("XMLHttpRequest", StringComparison.CurrentCultureIgnoreCase))
    //            {
    //                //Ajax请求内部错误
    //                ReturnModel returnModel = new ReturnModel();
    //                returnModel.Code = StatusCodes.Status500InternalServerError;
    //                returnModel.Msg = "系统发生错误，请联系管理员！";
    //                context.HttpContext.Response.ContentType = "application/json;charset=utf-8";
    //                context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
    //                await context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(returnModel));
    //            }
    //            else
    //            {

    //                context.Result = new RedirectResult("/Home/Error/500");
    //                //context.Result = new ContentResult
    //                //{
    //                //    Content = context.Exception.Message,//这里是把异常抛出。也可以不抛出。
    //                //    StatusCode = StatusCodes.Status200OK,
    //                //    ContentType = "text/html;charset=utf-8"
    //                //};
    //            }
    //        }
    //        //当你处理完异常之后，一定要将此属性更改为true，表示异常已经处理过了，这样其他地方就不会在处理这个异常
    //        context.ExceptionHandled = true;
    //    }
    //}
}
